package com.zhangjingcheng.yunyixia.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zhangjingcheng.yunyixia.constant.FileConstant;
import com.zhangjingcheng.yunyixia.constant.MessageConstant;
import com.zhangjingcheng.yunyixia.constant.StatusConstant;
import com.zhangjingcheng.yunyixia.em.SuffixType;
import com.zhangjingcheng.yunyixia.em.UpdateSpaceType;
import com.zhangjingcheng.yunyixia.exception.FileBusinessException;
import com.zhangjingcheng.yunyixia.exception.UserBusinessException;
import com.zhangjingcheng.yunyixia.mapper.FileMapper;
import com.zhangjingcheng.yunyixia.mapper.ShareMapper;
import com.zhangjingcheng.yunyixia.mapper.StoreMapper;
import com.zhangjingcheng.yunyixia.mapper.UserMapper;
import com.zhangjingcheng.yunyixia.pojo.dto.LoadFileListDTO;
import com.zhangjingcheng.yunyixia.pojo.dto.RenameDTO;
import com.zhangjingcheng.yunyixia.pojo.dto.UploadDTO;
import com.zhangjingcheng.yunyixia.pojo.entity.FileInfo;
import com.zhangjingcheng.yunyixia.pojo.entity.Share;
import com.zhangjingcheng.yunyixia.pojo.entity.Store;
import com.zhangjingcheng.yunyixia.pojo.entity.User;
import com.zhangjingcheng.yunyixia.pojo.vo.FileVO;
import com.zhangjingcheng.yunyixia.pojo.vo.UploadVO;
import com.zhangjingcheng.yunyixia.result.PageResult;
import com.zhangjingcheng.yunyixia.service.FileService;
import com.zhangjingcheng.yunyixia.service.UserService;
import com.zhangjingcheng.yunyixia.util.*;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Value("${temp.folder}")
    private String projectFolder;

    @Autowired
    private FileMapper fileMapper;

    @Autowired
    private S3UploadUtil s3UploadUtil;

    @Autowired
    private UserService userService;

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ShareMapper shareMapper;


    /**
     * 加载文件列表
     *
     * @param dto
     * @return
     */
    @Override
    public PageResult<FileVO> list(LoadFileListDTO dto) {
        log.info("开始加载文件列表：{}", dto);
        Page<FileVO> page = new Page<>(dto.getPageNum(), dto.getPageSize());
        if (dto.getUserId() == null) {
            dto.setUserId(ThreadLocalUtil.getId());
        }
        page = fileMapper.loadFileList(page, dto);
        return new PageResult<>(page.getTotal(), page.getRecords());
    }

    /**
     * S3文件上传
     *
     * @param file
     * @return
     */
    @Override
    public String s3Upload(MultipartFile file) {
        try {
            // 检查文件是否为空
            if (file.isEmpty()) {
                throw new FileBusinessException(MessageConstant.FILE_NOT_EMPTY);
            }

            String originalFilename = file.getOriginalFilename();
            // 检查文件名是否为空字符串
            if (originalFilename.isEmpty()) {
                throw new FileBusinessException(MessageConstant.FILE_NOT_EMPTY);
            }

            String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
            String accessUrl = s3UploadUtil.upload(file.getBytes(), fileName);
            log.info("访问路径为：{}", accessUrl);
            return accessUrl;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new FileBusinessException(MessageConstant.UPLOAD_FAILED);
        }
    }

    /**
     * 上传文件
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public UploadVO upload(UploadDTO dto) {
        // 获取用户id
        Integer userId = ThreadLocalUtil.getId();
        // 判断用户空间是否足够
        if (userService.getUserSpace().getUsedSpace() + dto.getSize() > userService.getUserSpace().getTotalSpace()) {
            throw new UserBusinessException(MessageConstant.USER_SPACE_NOT_ENOUGH);
        }
        // 当第一次上传时，检查文件是否已存在
        if (dto.getBlockIndex() == 0) {
            // 1. 通过MD5检查文件是否已存在于系统中
            FileInfo fileDb = findByMd5(dto.getMd5());

            if (fileDb != null) {
                // 2. 文件已存在（秒传逻辑）
                // 检查该用户是否已经拥有此文件，避免重复关联
                LambdaQueryWrapper<Store> storeQuery = new LambdaQueryWrapper<Store>()
                        .eq(Store::getUserId, userId)
                        .eq(Store::getFileId, fileDb.getId());

                // 如果文件已存在
                if (storeMapper.selectOne(storeQuery) != null) {
                    throw new FileBusinessException(MessageConstant.FILE_ALREADY_EXISTS);
                }
                // 用户仓库中没有此文件，添加关联
                UploadVO uploadVO = newStore(dto.getName(), userId, fileDb.getId(), dto.getSize());
                log.info("文件秒传成功，文件名：{}", dto.getName());
                return uploadVO;
            }
        }

        // 文件不存在，执行完整上传流程
        Path targetPath;
        File tempBlockFolder = null;
        try {
            // 确保临时文件夹存在
            File tempFolder = new File(projectFolder);
            if (!tempFolder.exists()) {
                tempFolder.mkdirs();
            }
            // 创建临时文件夹，放入分片文件
            tempBlockFolder = new File(projectFolder, dto.getMd5());
            if (dto.getBlockIndex() < dto.getBlockCount()) {
                if (!tempBlockFolder.exists()) {
                    tempBlockFolder.mkdirs();
                }
                File tempBlockFile = new File(tempBlockFolder, String.valueOf(dto.getBlockIndex()));
                // 使用 try-with-resources 自动关闭输入流
                try (InputStream inputStream = dto.getFile().getInputStream()) {
                    Files.copy(inputStream, tempBlockFile.toPath());
                } catch (IOException e) {
                    log.error("分块文件写入失败", e);
                    throw new FileBusinessException(MessageConstant.UPLOAD_FAILED);
                }
                // 创建上传中状态的UploadVO对象并返回
                UploadVO uploadingVO = new UploadVO();
                uploadingVO.setStatus(FileConstant.UPLOADING);
                if (dto.getBlockIndex() + 1 == dto.getBlockCount()) {

                    // 创建集合放入所有分片文件，从小到大排序
                    List<File> blockFiles = new ArrayList<>();
                    for (int i = 0; i < dto.getBlockCount(); i++) {
                        File blockFile = new File(tempBlockFolder, String.valueOf(i));
                        blockFiles.add(blockFile);
                    }
                    // 获取文件分类和后缀
                    String suffix = StringUtil.getSuffix(dto.getName(), SuffixType.NO_POINT.name());
                    Integer categoryId = CategoryUtil.getCategoryId(suffix);

                    // 创建新的文件名和目标路径
                    String newFileName = IdUtil.simpleUUID() + "." + suffix;
                    targetPath = Paths.get(projectFolder, newFileName);

                    // 使用NIO高效合并文件
                    try (OutputStream outputStream = Files.newOutputStream(targetPath)) {
                        for (File blockFile : blockFiles) {
                            // 再次检查文件是否存在
                            if (!blockFile.exists()) {
                                log.error("合并时分块文件不存在: {}", blockFile.getAbsolutePath());
                                throw new FileBusinessException(MessageConstant.UPLOAD_FAILED);
                            }
                            Files.copy(blockFile.toPath(), outputStream);
                        }
                    } catch (IOException e) {
                        log.error("合并文件失败", e);
                        throw new FileBusinessException(MessageConstant.UPLOAD_FAILED);
                    }
                    // 4. 将文件信息存入数据库
                    FileInfo fileInfo = FileInfo.builder()
                            .md5(dto.getMd5())
                            .name(dto.getName())
                            .path(targetPath.toString())
                            .size(dto.getSize())
                            .categoryId(categoryId)
                            .createTime(LocalDateTime.now())
                            .lastUpdateTime(LocalDateTime.now())
                            .build();
                    fileMapper.insert(fileInfo);

                    // 5. 将文件与用户关联
                    UploadVO uploadVO = newStore(dto.getName(), userId, fileInfo.getId(), dto.getSize());
                    log.info("文件上传成功，文件名：{}，路径：{}", dto.getName(), targetPath);
                    // 6.删除分块文件夹
                    FileUtil.del(tempBlockFolder);
                    return uploadVO;
                }
                return uploadingVO;
            } else {
                return new UploadVO();
            }
        } catch (Exception e) {
            log.error("文件上传处理失败", e);
            // 删除分块文件夹
            FileUtil.del(tempBlockFolder);
            throw new FileBusinessException(MessageConstant.UPLOAD_FAILED);
        }
    }

    /**
     * 新增文件与用户关联
     *
     * @param name
     * @param userId
     * @param fileId
     * @param size
     */
    private UploadVO newStore(String name, Integer userId, Integer fileId, Long size) {
        Store store = Store.builder()
                .userId(userId)
                .fileId(fileId) // 获取新插入记录的ID
                .delFlag(StatusConstant.ENABLE)
                .name(name)
                .createTime(LocalDateTime.now())
                .lastUpdateTime(LocalDateTime.now())
                .build();
        storeMapper.insert(store);

        // 6. 更新用户已用空间
        updateUserSpace(userId, size,UpdateSpaceType.ADD.name());

        // 7. 封装VO并返回
        UploadVO uploadVO = new UploadVO();
        uploadVO.setId(fileId);
        uploadVO.setStatus(FileConstant.UPLOAD_FINISH);
        return uploadVO;
    }

    /**
     * 根据md5查询文件
     *
     * @param md5
     * @return
     */
    @Override
    public FileInfo findByMd5(String md5) {
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<FileInfo>()
                .eq(FileInfo::getMd5, md5);
        return fileMapper.selectOne(queryWrapper);
    }

    /**
     * 获取图片
     *
     * @param id
     * @return
     */
    @Override
    public void getImage(Integer id, HttpServletResponse response) {
        FileInfo file = fileMapper.selectById(id);
        if (file == null) {
            throw new UserBusinessException(MessageConstant.FILE_NOT_EXISTS);
        }
        // 设置响应头
        response.setContentType(ContentTypeUtil.getContentType(file.getName()));
        response.setHeader("Cache-Control", "max-age=2592000"); // 缓存30天

        // 写入响应流
        try (OutputStream outputStream = response.getOutputStream()) {
            Files.copy(Paths.get(file.getPath()), outputStream);
            outputStream.flush();
        } catch (IOException e) {
            log.error("获取图片失败", e);
            throw new FileBusinessException(MessageConstant.DOWNLOAD_FAILED);
        }
    }


    /**
     * 根据名称查询文件
     *
     * @param name
     * @return
     */
    @Override
    public FileInfo findByName(String name) {
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<FileInfo>()
                .eq(FileInfo::getName, name);
        return fileMapper.selectOne(queryWrapper);
    }


    /**
     * 文件重命名
     *
     * @param dto
     */
    @Override
    public void rename(RenameDTO dto) {
        // 查询文件
        Store store = storeMapper.selectByFileId(dto.getId(), ThreadLocalUtil.getId());
        if (store == null) {
            throw new UserBusinessException(MessageConstant.FILE_NOT_EXISTS);
        }
        // 文件重命名
        Store newStore = Store.builder()
                .id(store.getId())
                .name(dto.getName())
                .lastUpdateTime(LocalDateTime.now())
                .build();
        storeMapper.updateById(newStore);
    }


    /**
     * 更新文件删除标志,进出回收站
     *
     * @param ids
     */
    @Override
    @Transactional
    public void updateFlag(List<Integer> ids) {
        ids.forEach(id -> {
            // 查询文件
            Store store = storeMapper.selectByFileId(id, ThreadLocalUtil.getId());
            if (store == null) {
                throw new UserBusinessException(MessageConstant.FILE_NOT_EXISTS);
            }
            // 更新文件删除标志
            doUpdateFlag(store);

        });
    }


    /**
     * 彻底删除文件
     *
     * @param ids
     */
    @Override
    @Transactional
    public void deleteByIds(List<Integer> ids) {
        ids.forEach(id -> {
            Store store = storeMapper.selectByFileId(id, ThreadLocalUtil.getId());
            if (store == null) {
                throw new UserBusinessException(MessageConstant.FILE_NOT_EXISTS);
            }
            // 判断删除状态
            if (!store.getDelFlag().equals(StatusConstant.DISABLE)) {
                throw new UserBusinessException(MessageConstant.UNKNOWN_ERROR);
            }
            deleteFile(id);
        });
    }

    /**
     * 管理员删除文件
     *
     * @param ids
     */
    @Override
    @Transactional
    public void AdminDelFile(List<Integer> ids) {
        List<FileInfo> fileList = fileMapper.selectByIds(ids);
        // 批量删除
        if (fileList != null && !fileList.isEmpty()) {
            fileList.forEach(file -> {
                // 判断文件是否存在
                if (file != null) {
                    // 找到拥有文件的用户
                    List<User> userList = userMapper.selectByFileId(file.getId());

                    // 删除分享记录
                    LambdaQueryWrapper<Share> shareQueryWrapper = new LambdaQueryWrapper<Share>()
                            .eq(Share::getFileId, file.getId());
                    shareMapper.delete(shareQueryWrapper);

                    // 批量删除仓库
                    LambdaQueryWrapper<Store> queryWrapper = new LambdaQueryWrapper<Store>()
                            .eq(Store::getFileId, file.getId());
                    storeMapper.delete(queryWrapper);
                    userList.forEach(user -> {
                        // 更新用户空间
                        updateUserSpace(user.getId(), file.getSize(), UpdateSpaceType.SUB.name());
                    });
                    // 删除文件
                    fileMapper.deleteById(file.getId());
                }
            });
        }
    }

    /**
     * 管理员重命名文件
     *
     * @param dto
     */
    @Override
    public void adminRenameFile(RenameDTO dto) {
        // 重命名文件
        FileInfo file = FileInfo.builder()
                .id(dto.getId())
                .name(dto.getName())
                .lastUpdateTime(LocalDateTime.now())
                .build();
        fileMapper.updateById(file);
    }

    /**
     * 文件下载
     *
     * @param id
     * @param response
     */
    @Override
    public void download(Integer id, HttpServletResponse response) {
        // 判断文件是否存在
        FileInfo fileDb = fileMapper.selectById(id);
        if (fileDb == null) {
            throw new UserBusinessException(MessageConstant.FILE_NOT_EXISTS);
        }

        try {
            // 获取文件名并进行URL编码，防止中文乱码
            String fileName = URLEncoder.encode(fileDb.getName(), StandardCharsets.UTF_8);

            // 设置响应头信息
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
            response.setContentType(ContentTypeUtil.getContentType(fileDb.getName()));
            response.setContentLengthLong(fileDb.getSize());

            // 写入浏览器
            try (OutputStream outputStream = response.getOutputStream()) {
                Files.copy(Path.of(fileDb.getPath()), outputStream);
            }
        } catch (Exception e) {
            throw new FileBusinessException(MessageConstant.DOWNLOAD_FAILED);
        }
    }

    /**
     * 获取所有文件
     *
     * @return
     */
    @Override
    public PageResult<FileVO> getAllFile(LoadFileListDTO dto) {
        Page<FileVO> page = new Page<>(dto.getPageNum(), dto.getPageSize());
        page = fileMapper.AdminLoadFileList(page, dto);
        return new PageResult<>(page.getTotal(), page.getRecords());
    }


    // 执行更新操作
    private void doUpdateFlag(Store store) {
        if (store == null) {
            throw new FileBusinessException(MessageConstant.FILE_NOT_EXISTS);
        }
        Store storeToUpdate = new Store();
        // 改变文件删除标志
        Integer flag = store.getDelFlag();
        if (flag.equals(StatusConstant.DISABLE)) {
            flag = StatusConstant.ENABLE;
        } else {
            flag = StatusConstant.DISABLE;
            storeToUpdate.setRecoverTime(LocalDateTime.now());
        }
        storeToUpdate.setId(store.getId());
        storeToUpdate.setDelFlag(flag);
        storeToUpdate.setLastUpdateTime(LocalDateTime.now());
        storeMapper.updateById(storeToUpdate);
    }


    /**
     * 删除文件(用户仓库层面）
     *
     * @param id
     */
    private void deleteFile(Integer id) {
        // 获取文件的空间
        FileInfo file = fileMapper.selectById(id);
        Long size = file.getSize();
        // 执行删除
        LambdaQueryWrapper<Store> queryWrapper = new LambdaQueryWrapper<Store>()
                .eq(Store::getFileId, id)
                .eq(Store::getUserId, ThreadLocalUtil.getId());
        storeMapper.delete(queryWrapper);
        // 删除文件
        Integer count = storeMapper.countFileId(id);
        if (count == 1) {
            String path = file.getPath();
            if (path != null && !path.isEmpty()) {
                FileUtil.del(path);
            }
        }
        // 文件大小为0，则不更新用户空间
        if (size == 0) {
            return;
        }
        // 更新用户空间
        updateUserSpace(ThreadLocalUtil.getId(), size, UpdateSpaceType.SUB.name());
    }

    /**
     * 更新用户空间
     *
     * @param size
     */
    @Override
    public void updateUserSpace(Integer id, Long size, String type) {
        if (UpdateSpaceType.ADD.name().equals(type)) {
            // 添加用户已使用空间
            User newUser = User.builder()
                    .id(id)
                    .usedSpace(size)
                    .build();
            userMapper.updateUser(newUser);
        } else if (UpdateSpaceType.SUB.name().equals(type)) {
            // 减少用户已使用空间
            User newUser = User.builder()
                    .id(id)
                    .usedSpace(-size)
                    .build();
            userMapper.updateUser(newUser);
        }
    }


}
